package educrypt.database.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;

import educrypt.analysis.Frequencies;
import educrypt.analysis.FrequencyAnalysis;
import educrypt.commons.Alphabet;
import educrypt.commons.Text;
import educrypt.database.Connector;
import educrypt.exceptions.EduCryptException;
import educrypt.i18n.Messages;

public class FrequencyAnalysisDAO extends Observable {

	/* Singleton */
	private FrequencyAnalysisDAO() {
	}

	public static FrequencyAnalysisDAO getInstance() {
		return SingletonHolder.instance;
	}

	private static class SingletonHolder {
		public static final FrequencyAnalysisDAO instance = new FrequencyAnalysisDAO();
	}

	/* END Singleton */

	public boolean exists(FrequencyAnalysis analysis) {
		boolean exists = false;
		String sqlExistsAnalysis = "SELECT COUNT(*) FROM FrequencyAnalysis WHERE textId=? AND alphabetName=? AND ngramSize=?";
		int textId = TextDAO.getInstance().findId(
				analysis.getText().getContentHash());
		try {
			Connection conn = Connector.getConnection();
			PreparedStatement ps = conn.prepareStatement(sqlExistsAnalysis);
			ps.setInt(1, textId);
			ps.setString(2, analysis.getAlphabet().getName());
			ps.setInt(3, analysis.getNgramSize());
			ResultSet rs = ps.executeQuery();
			if (rs.next()) {
				exists = rs.getInt(1) > 0;
			}
			rs.close();
			ps.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return exists;
	}

	public void create(FrequencyAnalysis analysis) {
		String sqlCreateFrequencyAnalysis = "INSERT INTO FrequencyAnalysis "
				+ "(textId,alphabetName,ngramSize,ngrams,ocurrences,absoluteOcurrences) VALUES (?,?,?,?,?,?)";
		int idText = TextDAO.getInstance().create(analysis.getText());
		if (!exists(analysis)) {
			try {
				Connection conn = Connector.getConnection();
				PreparedStatement ps = conn
						.prepareStatement(sqlCreateFrequencyAnalysis);
				ps.setInt(1, idText);
				ps.setString(2, analysis.getAlphabet().getName());
				ps.setInt(3, analysis.getNgramSize());
				ps.setObject(4, analysis.getFrequencies().getNgrams());
				ps.setObject(5, analysis.getFrequencies().getValues());
				ps.setObject(6, analysis.getFrequencies().getAbsoluteValues());

				ps.executeUpdate();
				ps.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			this.setChanged();
			this.notifyObservers();
		}
	}

	public List<FrequencyAnalysis> findByTextId(Text text) {
		List<FrequencyAnalysis> analysis = new ArrayList<FrequencyAnalysis>();

		int textId = TextDAO.getInstance().findId(text.getContentHash());

		String sqlFindFreqAnalysisByTextId = "SELECT * FROM FrequencyAnalysis WHERE textId = ?";
		try {
			Connection conn = Connector.getConnection();
			PreparedStatement ps = conn
					.prepareStatement(sqlFindFreqAnalysisByTextId);
			ps.setInt(1, textId);
			ResultSet rs = ps.executeQuery();
			String alphabetName = "";
			int ngramSize;
			while (rs.next()) {
				alphabetName = rs.getString("alphabetName");
				ngramSize = rs.getInt("ngramSize");
				Alphabet alphabet = AlphabetDAO.getInstance()
						.find(alphabetName);
				Object[] auxNgrams = (Object[]) rs.getObject("ngrams");
				String[] ngrams = objectArrayToStringArray(auxNgrams);
				Object[] auxOcurrences = (Object[]) rs.getObject("ocurrences");
				double[] ocurrences = objectArrayToDoubleArray(auxOcurrences);
				Object[] auxAbsoluteOcurrences = (Object[]) rs
						.getObject("absoluteOcurrences");
				double[] absoluteOcurrences = objectArrayToDoubleArray(auxAbsoluteOcurrences);

				Frequencies frequencies = new Frequencies(ngrams, ocurrences,
						absoluteOcurrences);
				FrequencyAnalysis fa = new FrequencyAnalysis(text, alphabet,
						ngramSize);
				fa.setFrequencies(frequencies);

				analysis.add(fa);

			}
			rs.close();
			ps.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return analysis;
	}

	public void delete(FrequencyAnalysis frequencyAnalysis) {
		String alphabetName = frequencyAnalysis.getAlphabet().getName();
		int ngramSize = frequencyAnalysis.getNgramSize();
		String textHash = frequencyAnalysis.getText().getContentHash();

		int textId = TextDAO.getInstance().findId(textHash);

		final String sqlDeleteAnalysis = "DELETE FROM FrequencyAnalysis WHERE alphabetName=? AND ngramSize=? AND textId=?";
		try {
			Connection conn = Connector.getConnection();
			PreparedStatement ps = conn.prepareStatement(sqlDeleteAnalysis);
			ps.setString(1, alphabetName);
			ps.setInt(2, ngramSize);
			ps.setInt(3, textId);
			ps.executeUpdate();
			ps.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		this.setChanged();
		this.notifyObservers();
	}

	public Frequencies getAverageFrequencies(String alphabetFilter,
			int ngramSizeFilter, String textLanguageFilter,
			String textTypeFilter) throws EduCryptException {
		String sqlGetAverageFrequencies = generateAverageFrequenciesQuery(
				alphabetFilter, ngramSizeFilter, textLanguageFilter,
				textTypeFilter);

		Frequencies averageFrequencies = null;

		try {
			Connection conn = Connector.getConnection();
			Statement st = conn.createStatement();
			ResultSet rs = st.executeQuery(sqlGetAverageFrequencies);
			List<double[]> allOccurrences = new ArrayList<double[]>();
			String[] ngrams = null;
			boolean noResultReturned = !rs.isBeforeFirst();
			if (noResultReturned) {
				throw new EduCryptException(
						Messages.getString("errBDNoResultReturned"));
			}
			while (rs.next()) {
				Object[] auxNgrams = (Object[]) rs.getObject("ngrams");
				ngrams = objectArrayToStringArray(auxNgrams);
				Object[] auxOccurrences = (Object[]) rs.getObject("ocurrences");
				double[] occurrences = objectArrayToDoubleArray(auxOccurrences);
				allOccurrences.add(occurrences);
			}
			double[] averageOccurrences = calculateAverage(allOccurrences);

			averageFrequencies = new Frequencies(ngrams, averageOccurrences);

		} catch (SQLException e) {
			e.printStackTrace();
		}
		return averageFrequencies;
	}

	private static double[] objectArrayToDoubleArray(Object[] objectArray) {
		double[] doubleArray = new double[objectArray.length];
		for (int i = 0; i < objectArray.length; i++) {
			doubleArray[i] = (Double) objectArray[i];
		}
		return doubleArray;

	}

	private static String[] objectArrayToStringArray(Object[] objectArray) {
		String[] stringArray = new String[objectArray.length];
		for (int i = 0; i < objectArray.length; i++) {
			stringArray[i] = (String) objectArray[i];
		}
		return stringArray;
	}

	private static double[] calculateAverage(List<double[]> allOccurrences) {
		double[] averageOcurrences = new double[allOccurrences.get(0).length];
		for (double[] actualOccurrences : allOccurrences) {
			for (int i = 0; i < actualOccurrences.length; i++) {
				averageOcurrences[i] = averageOcurrences[i]
						+ actualOccurrences[i];
			}
		}
		for (int i = 0; i < averageOcurrences.length; i++) {
			averageOcurrences[i] = averageOcurrences[i] / allOccurrences.size();
		}
		return averageOcurrences;
	}

	private static String generateAverageFrequenciesQuery(
			String alphabetFilter, int ngramSizeFilter,
			String textLanguageFilter, String textTypeFilter) {
		String sqlGetAverageFrequencies = "SELECT ngrams, ocurrences FROM FrequencyAnalysis WHERE alphabetName='"
				+ alphabetFilter + "' AND ngramSize=" + ngramSizeFilter;

		if (textTypeFilter != null) {
			sqlGetAverageFrequencies += " AND textId IN (SELECT id FROM Text WHERE textType='"
					+ textTypeFilter
					+ "' AND textLanguage='"
					+ textLanguageFilter + "')";
		} else {
			sqlGetAverageFrequencies += " AND textId IN (SELECT id FROM Text WHERE textLanguage='"
					+ textLanguageFilter + "')";
		}
		return sqlGetAverageFrequencies;
	}
}
